﻿using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Printing;
using System.Drawing.Imaging;
using System.Xml;
using System.Threading;
using System.IO;
using System.Windows.Media.Imaging;

namespace SharppiFlowEditor.models
{
    /*
    * The delegate for diagram change events.
    */
    public delegate void DiagramEventHandler(object sender, FlowChartEventArgs e);

    /*
     * The model class for diagrams.
     */
    public class AppModel
    {

        private ArrayList documents = null;
        
        public AppModel()
        {
            documents = new ArrayList();
        }

        public Document NewDocument(string name, int width, int height)
        {
            Document document = new Document();
            document.Documentsize = new Size(width, height);
            document.DocumentName = name;
            documents.Add(document);
            return document;
        }

        public void ChangeDocumentName(Document d, String name)
        {
            Document document = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                document.DocumentName = name;
            }

            OnDiagramChange(new FlowChartEventArgs(true));

        }

        public void ChangeDocumentSize(Document d, Size size)
        {
            Document document = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                document.Documentsize = size;
            }
            OnDiagramChange(new FlowChartEventArgs(true));
        }


        
        // if d is found, create and return new node, otherwise return null
        public AbstractNode createAbstractNodeRENAMETHISMETHODLATER(Document d, int x, int y, int width, int height, string title, int type)
        {
            Document document = null;
            AbstractNode an = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                
                switch (type)
                {
                    case 1:
                        an = new EndNode(x, y, width, height);
                        an.Title = title;
                        an.BGColor = Color.HotPink;
                        an.Type = type;
                        document.Nodes.Add(an);
                        OnDiagramChange(new FlowChartEventArgs(an, true));
                        return an;
                    case 2:
                        an = new ActionNode(x, y, width, height);
                        an.Title = title;
                        an.BGColor = Color.Azure;
                        an.Type = type;
                        document.Nodes.Add(an);
                        OnDiagramChange(new FlowChartEventArgs(an, true));
                        return an;
                    case 3:
                        an = new ConditionalNode(x, y, width, height);
                        an.Title = title;
                        an.Type = type;
                        an.BGColor = Color.LightGreen;
                        document.Nodes.Add(an);
                        OnDiagramChange(new FlowChartEventArgs(an, true));
                        return an;
                }
            }
           
            return an;
        }

        // If d is found:
        // Create a new connector and the change event, n1 and n2 = the connected nodes and return it
        // Otherwise return null
        public Connector CreateConnector(Document d, AbstractNode n1, AbstractNode n2, string text)
        {
            Document document = null;
            Connector c = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                if (text.Length == 0)
                    c = new Connector(n1, n2);
                else
                    c = new Connector(n1, n2, text);
                document.Connectors.Add(c);
                // raise the change event
                OnDiagramChange(new FlowChartEventArgs(c, true));
            }
            return c;
        }

        // If d is found
        // Get and return the connectors attached to the specified node.
        // Otherwise return null
        public Connector[] GetNodeConnectors(Document d, AbstractNode n)
        {
            Document document = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                ArrayList conns = new ArrayList();
                IEnumerator en = document.Connectors.GetEnumerator();
                while (en.MoveNext())
                {
                    Connector c = (Connector)en.Current;
                    if (c.Node1 == n || c.Node2 == n)
                    {
                        conns.Add(c);
                    }
                }
                return (Connector[])conns.ToArray(Type.GetType("SharppiFlowEditor.models.Connector"));
            }
            return null;
        }

        /*
         * Move a node and create the necessary change events.
         *  n = the moving node
         *  x and y = the new center point
         */
        public void MoveNode(Document d, AbstractNode n, float x, float y)
        {
            Document document = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                if (n.getBoundingRect().X != x || n.getBoundingRect().Y != y)
                {
                    Connector[] conns = GetNodeConnectors(document, n);
                    // raise a change event for each connector in original state
                    IEnumerator en = document.Connectors.GetEnumerator();
                    while (en.MoveNext())
                    {
                        Connector c = (Connector)en.Current;
                        OnDiagramChange(new FlowChartEventArgs(c, false));
                    }
                    // raise a change event for the node in the old location
                    OnDiagramChange(new FlowChartEventArgs(n, false));

                    n.setBoundingRectLocation((int)x, (int)y);

                    // raise a change event for each connector in new state
                    en.Reset();
                    while (en.MoveNext())
                    {
                        Connector c = (Connector)en.Current;
                        OnDiagramChange(new FlowChartEventArgs(c, false));
                    }
                    // raise a change event for the node in new location
                    // last is true
                    OnDiagramChange(new FlowChartEventArgs(n, true));
                }
            }
        }

        public void DeleteConnector(Document d, Connector con)
        {
            Document document = null;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;

                    foreach (Connector c in document.Connectors)
                    {
                        if (c.Node1 == con.Node1 && c.Node2 == con.Node2)
                        {
                           document.Connectors.Remove(c);
                           break;
                        }
                    }
                }
            }
        }

        // Are the specified node already connected?
        public bool Connected(Document d, AbstractNode n1, AbstractNode n2)
        {
            Document document = null;
            bool connected = false;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                IEnumerator en = document.Connectors.GetEnumerator();
                while (en.MoveNext())
                {
                    Connector c = (Connector)en.Current;
                    if ((c.Node1 == n1 && c.Node2 == n2) || (c.Node1 == n2 && c.Node2 == n1))
                    {
                        connected = true;
                    }
                }
            }
            return connected;
        }

        // The event for diagram change events.
        public event DiagramEventHandler DiagramChange;

        // Notify the event delegates about an event.
        protected virtual void OnDiagramChange(FlowChartEventArgs e)
        {
            if (DiagramChange != null)
            {
                // invoke the delegates
                DiagramChange(this, e);
            }
        }

        // Delete node
        internal bool DeleteNode(Document d, AbstractNode n)
        {
            Document document = null;
            bool deleted = false;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                for (int i = 0; i < document.Nodes.Count; i++)
                {
                    Connector[] conns = GetNodeConnectors(document, n);
                    if (conns.Length > 0)
                    {
                        for (int j = 0; j < document.Connectors.Count; j++)
                        {
                            Connector c = (Connector)document.Connectors[j];
                            if (c.Node1.Equals(n) || c.Node2.Equals(n))
                                document.Connectors.RemoveAt(j);
                        }
                    }
                    if (document.Nodes[i].Equals(n))
                    {
                        document.Nodes.RemoveAt(i);
                        deleted = true;
                    }
                }
            }
            return deleted;
        }


        public bool saveDocument(Document d, string filePath)
        {
            Document document = null;
            bool save = false;
            IEnumerator e = documents.GetEnumerator();
            while (e.MoveNext())
            {
                if (((Document)(e.Current)).DocumentName == d.DocumentName)
                {
                    document = d;
                }
            }
            if (document != null)
            {
                sortNodes(document);
                if (document.SaveFilePath == "")
                {
                    document.SaveFilePath = filePath;
                }

                XmlWriter xw = XmlWriter.Create(document.SaveFilePath);

                xw.WriteStartDocument();
                xw.WriteStartElement("flowchart");
                xw.WriteStartElement("documentproperties");
                xw.WriteStartElement("documentsize");
                xw.WriteAttributeString("width", document.Documentsize.Width.ToString());
                xw.WriteAttributeString("height", document.Documentsize.Height.ToString());
                xw.WriteEndElement();
                xw.WriteStartElement("filepath");
                xw.WriteAttributeString("path", document.SaveFilePath);
                xw.WriteEndElement();
                xw.WriteEndElement();
                xw.WriteStartElement("nodes");
                foreach (AbstractNode an in document.Nodes)
                {
                    xw.WriteStartElement("node");
                    xw.WriteAttributeString("id", document.Nodes.IndexOf(an).ToString());
                    xw.WriteStartElement("type");
                    xw.WriteString(an.Type.ToString());
                    xw.WriteEndElement();

                    xw.WriteStartElement("title");
                    xw.WriteString(an.Title);
                    xw.WriteEndElement();

                    xw.WriteStartElement("xcoord");
                    xw.WriteString(an.getBoundingRect().X.ToString());
                    xw.WriteEndElement();

                    xw.WriteStartElement("ycoord");
                    xw.WriteString(an.getBoundingRect().Y.ToString());
                    xw.WriteEndElement();

                    xw.WriteEndElement();
                }
                xw.WriteEndElement();

                xw.WriteStartElement("connectors");
                foreach (Connector con in document.Connectors)
                {
                    xw.WriteStartElement("connector");
                    xw.WriteAttributeString("id", "asdf");
                    xw.WriteStartElement("node1");
                    xw.WriteString(document.Nodes.IndexOf(con.Node1).ToString());
                    xw.WriteEndElement();

                    xw.WriteStartElement("node2");
                    xw.WriteString(document.Nodes.IndexOf(con.Node2).ToString());
                    xw.WriteEndElement();

                    xw.WriteStartElement("text");
                    xw.WriteString(con.Text);
                    xw.WriteEndElement();

                    xw.WriteEndElement();
                }
                xw.WriteEndElement();

                xw.WriteEndElement();
                xw.WriteEndDocument();
                xw.Close();
                save = true;
            }
            return save;
        }

        // returns opened document or null, if document cannot be opened
        public Document openDocument(Stream fileContent)
        {
            XmlReader xr = XmlReader.Create(fileContent);

            Document document = null;

            while (xr.Read())
            {
                if (xr.IsStartElement() && xr.Name == "documentsize")
                {
                    document = new Document();
                    document.Documentsize = new System.Drawing.Size(Convert.ToInt32(xr.GetAttribute(0)), Convert.ToInt32(xr.GetAttribute(1)));
                }
                if (xr.IsStartElement() && xr.Name == "filepath")
                {
                    document.SaveFilePath = xr.GetAttribute(0);
                    // Determine where last backslash is.
                    int lastindex = document.SaveFilePath.LastIndexOf('\\');
                    // If there is no backslash, assume that this is a filename.
                    if (lastindex == -1)
                    {
                        document.DocumentName = document.SaveFilePath;
                    }
                    else
                    {
                        document.DocumentName = document.SaveFilePath.Remove(0, lastindex + 2);

                        this.documents.Add(document);
                    }
                }

                if (xr.IsStartElement() && xr.Name == "node")
                {
                    xr.Read(); // Read the start tag.
                    if (xr.IsStartElement())  // Handle nested elements.
                        Console.Write("\r\n<{0}>", xr.Name);
                    int type = Convert.ToInt32(xr.ReadString());  //Read the text content of the element.

                    xr.Read(); // Read the start tag.
                    if (xr.IsStartElement())  // Handle nested elements.
                        Console.Write("\r\n<{0}>", xr.Name);
                    string title = xr.ReadString();

                    xr.Read(); // Read the start tag.
                    if (xr.IsStartElement())  // Handle nested elements.
                        Console.Write("\r\n<{0}>", xr.Name);
                    int xcoord = Convert.ToInt32(xr.ReadString());  //Read the text content of the element.

                    xr.Read(); // Read the start tag.
                    if (xr.IsStartElement())  // Handle nested elements.
                        Console.Write("\r\n<{0}>", xr.Name);
                    int ycoord = Convert.ToInt32(xr.ReadString());  //Read the text content of the element.

                    createAbstractNodeRENAMETHISMETHODLATER(document, xcoord, ycoord, 20, 20, title, type);
                    //Thread.Sleep(500);
                }

                if (xr.IsStartElement() && xr.Name == "connector")
                {
                    AbstractNode an1 = null; 
                    AbstractNode an2 = null;

                    xr.Read(); // Read the start tag.
                    if (xr.IsStartElement())
                    {
                        //Console.WriteLine(xr.Name + "::" + Convert.ToInt32(xr.ReadString()));
                        int b = Convert.ToInt32(xr.ReadString());
                        an1 = (AbstractNode) document.Nodes[b];  //Read the text content of the element.
                    }

                    xr.Read(); // Read the start tag.
                    if (xr.IsStartElement())
                    {
                        int b = Convert.ToInt32(xr.ReadString());
                        an2 = (AbstractNode)document.Nodes[b];  //Read the text content of the element.
                    }

                    xr.Read(); // Read the start tag.                                           
                        string text = xr.ReadString();
                    CreateConnector(document, an1, an2, text);
                    //Thread.Sleep(500);
                }
            }
            return document;
            
        }


        public bool exportImage(Bitmap image, string filePath)
        {
            bool export = false;

            //FileStream stream = new FileStream("new.jpg", FileMode.Create);
            //JpegBitmapEncoder encoder = new JpegBitmapEncoder();
            ////TextBlock myTextBlock = new TextBlock();
            ////myTextBlock.Text = "Codec Author is: " + encoder.CodecInfo.Author.ToString();
            //encoder.FlipHorizontal = false;
            //encoder.FlipVertical = false;
            //encoder.QualityLevel = 100;
            ////encoder.Rotation = Rotation.Rotate90;
            //encoder.Frames.Add(BitmapFrame.Create(image));
            //encoder.Save(stream);
            //FileStream fs = new FileStream();
            if (image != null)
            {
                ImageCodecInfo codecInfo = GetEncoderInfo("image/jpeg");
                EncoderParameters encoderParams = new EncoderParameters(1);
                EncoderParameter encoderQuality = new EncoderParameter(Encoder.Quality, 100L);
                encoderParams.Param[0] = encoderQuality;

                image.Save(filePath, codecInfo, encoderParams);
                export = true;
            }

            return export;
        }

        private static ImageCodecInfo GetEncoderInfo(String mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }


        //bubble sort algorithm, used to sort the nodes so when the diagram is loaded, the nodes will be drawn in a logical order
        public static void sortNodes(Document document)
        {
            for (int i = document.Nodes.Count - 1; i > 0; i--)
            {
                for (int j = 0; j < document.Nodes.Count - 1; j++)
                {
                    AbstractNode anFirst = (AbstractNode)document.Nodes[j];
                    AbstractNode anSecond = (AbstractNode)document.Nodes[j + 1];

                    if (anFirst.getBoundingRect().Y > anSecond.getBoundingRect().Y)
                    {
                        AbstractNode anTemp = anFirst;
                        document.Nodes[j] = anSecond;
                        document.Nodes[j + 1] = anTemp;
                    }
                }
            }
        }
    }
}